package com.canyou.integration;

import com.canyou.enums.ErrorCodeEnum;
import com.canyou.event.UserLoginEvent;
import com.canyou.exception.CysBusinessException;
import com.canyou.service.IntegrationUserDetailsServiceImpl;
import com.canyou.service.LoginAttemptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 自定义的用户名密码认证实现类
 *
 * @author cgz
 * @author cc
 * @date 2019-06-13
 */
@Slf4j
@Component("userPwdAuthenticationProvider")
public class UserPwdAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private IntegrationUserDetailsServiceImpl userDetailsServiceImpl;
    @Autowired
    private LoginAttemptService loginAttemptService;
    @Autowired
    ApplicationContext applicationContext;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 将未认证的Authentication转换成自定义的用户认证Token
        UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) authentication;
        String account = (String) (authenticationToken.getPrincipal());
        String password = (String) (authenticationToken.getCredentials());
        // 根据用户Token中的用户名查找用户信息，如果有该用户信息，则验证用户密码是否正确
        UserDetails user = userDetailsServiceImpl.loadUserByUsername(account);
        if (user == null) {
            throw new CysBusinessException(ErrorCodeEnum.USER_LOCKED, "用户名或密码不正确");
        }
        // 登录状态，0：成功；1：失败
        int loginStatus = 0;
        // 最大错误次数
        int maxFailCount = 5;
        String errorMsg = "";
        String username = user.getUsername();
        if (!user.isEnabled()) {
            errorMsg = "您的账户已禁用，请联系管理员。";
            loginStatus = 1;
        } else if (!user.isAccountNonLocked()) {
            errorMsg = "用户名或密码错误次数过多，您的账户被系统锁定5分钟，请稍后再试。";
            loginStatus = 1;
        } else if (loginAttemptService.isLocked(username)) {
            errorMsg = "您的账户已被锁定，请5分钟后再试。";
            loginStatus = 1;
        } else if (!this.passwordEncoder.matches(password, user.getPassword())) {
            loginAttemptService.loginFailed(username);
            int failCount = loginAttemptService.getAttempts(username);
            int leftCount = loginAttemptService.leftCount(username);
            errorMsg = "用户名或密码不正确";
            if (failCount > 1) {
                errorMsg += ";连续错误5次将锁定账号5分钟,剩余" + leftCount + "次";
            }
            if (failCount >= maxFailCount) {
                errorMsg = "您的账户已被锁定，请5分钟后再试。";
            }
            loginStatus = 1;
        }
        // 发布登录事件
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        applicationContext.publishEvent(new UserLoginEvent(this, attributes.getRequest(), loginStatus, errorMsg, username));
        if (loginStatus == 1) {
            throw new CysBusinessException(ErrorCodeEnum.USER_LOCKED, errorMsg);
        }
        // 登录成功，清除缓存记录的登录失败次数
        loginAttemptService.loginSucceeded(username);
        // 认证成功则创建一个已认证的用户认证Token
        UsernamePasswordAuthenticationToken authenticationResult = new UsernamePasswordAuthenticationToken(user,
                user.getPassword(), user.getAuthorities());
        // 设置一些详情信息
        authenticationResult.setDetails(authenticationToken.getDetails());
        return authenticationResult;
    }


    @Override
    public boolean supports(Class<?> authentication) {
        // 指定该认证处理器能对CustomUsernamePasswordAuthenticationToken对象进行认证
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}
